﻿namespace NTLib.Pipe.Targets
{
    using NTLib.Core.Tasks;
    using NTLib.Core.Threading;
    using System;
    using System.Reflection;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Threading;
    using NTLib.Pipe.Descriptors;
    using NTLib.Core.Component;

    /// <summary>
    /// Manager used to transform basic information to a target usable to produce a pipe.
    /// </summary>
    public class TargetManager : IComponentLoader<ITargetProvider>
    {
        #region Fields

        private static readonly TargetManager s_instance = new TargetManager();
        private readonly AsyncLock _locatorLock;
        private readonly Dictionary<TypeInfo, List<ITargetProvider>> _providers;
        private readonly Dictionary<Guid, ITargetProvider> _providersById;

        #endregion

        #region CTOR

        /// <summary>
        /// Initializes the <see cref="TargetManager"/> class.
        /// </summary>
        private TargetManager()
        {
            this._locatorLock = new AsyncLock();
            this._providers = new Dictionary<TypeInfo, List<ITargetProvider>>();
            this._providersById = new Dictionary<Guid, ITargetProvider>();

            this.Register(new LocalTargetProvider());
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the instance.
        /// </summary>
        public static TargetManager Instance
        {
            get { return s_instance; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Locates the target using the info give in parameters and return all the possible ways to achived to the target.
        /// </summary>
        /// <typeparam name="U">Type of the information container.</typeparam>
        /// <param name="info">The information use to locate the specifique target.</param>
        /// <returns>All the possible way to reach the target.</returns>
        public async Task<IEnumerable<ITargetDescriptor>> LocateAsync<U>(U info = default(U), CancellationToken token = default(CancellationToken))
        {
            if (object.Equals(info, default(U)))
                return new ITargetDescriptor[] { LocalTargetDescription.Instance };

            using (var lck = await this._locatorLock.LockAsync())
            {

                token.ThrowIfCancellationRequested();

                var infoType = typeof(U).GetTypeInfo();
                IEnumerable<ITargetProvider> providers = (from p in this._providers
                                                          where p.Key.IsAssignableFrom(infoType)
                                                          select p.Value).Aggregate(new List<ITargetProvider>(), (acc, res) =>
                                                          {
                                                              foreach (var r in res)
                                                              {
                                                                  if (!acc.Contains(r) && r.IsEnabled)
                                                                      acc.Add(r);
                                                              }

                                                              token.ThrowIfCancellationRequested();

                                                              return acc;
                                                          });
                token.ThrowIfCancellationRequested();

                List<ITargetDescriptor> desciptions = new List<ITargetDescriptor>();
                List<Task<IEnumerable<ITargetDescriptor>>> tasks = new List<Task<IEnumerable<ITargetDescriptor>>>();
                foreach (var p in providers)
                {
                    tasks.Add(p.LocateAsync(info, token));
                }

                await Task.WhenAll(tasks);

                token.ThrowIfCancellationRequested();

                var targets = tasks.Aggregate(new List<ITargetDescriptor>(), (acc, elems) =>
                {
                    if (elems.IsFaulted)
                        return acc;
                    var results = elems.Result;

                    if (results != null)
                    {
                        foreach (var r in results)
                        {
                            if (!acc.Contains(r))
                                acc.Add(r);
                        }

                        token.ThrowIfCancellationRequested();
                    }
                    return acc;
                });

                token.ThrowIfCancellationRequested();

                return targets;
            }
        }

        /// <summary>
        /// Registers the specified provider.
        /// </summary>
        public Guid Register<U>() where U : ITargetProvider
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Registers the specified provider.
        /// </summary>
        public Guid Register(Type type)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Registers the provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        public Guid Register(ITargetProvider provider)
        {
            using (this._locatorLock.LockAsync().WaitResult())
            {
                if (this._providersById.ContainsKey(provider.Id))
                    return provider.Id;

                var types = provider.InfoTypeManaged;

                foreach (var tp in types)
                {
                    List<ITargetProvider> providers = null;
                    var info = tp.GetTypeInfo();
                    if (!this._providers.TryGetValue(info, out providers))
                    {
                        providers = new List<ITargetProvider>();
                        this._providers.Add(info, providers);
                    }
                    if (!providers.Contains(provider))
                        providers.Add(provider);
                }

                this._providersById.Add(provider.Id, provider);
            }
            return provider.Id;
        }

        /// <summary>
        /// Unregister provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        public void UnRegister<U>()
            where U : ITargetProvider
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Unregister provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        public void UnRegister(Type Type)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Unregister provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        public void UnRegister(ITargetProvider provider)
        {
            using (this._locatorLock.LockAsync().WaitResult())
            {
                var types = provider.InfoTypeManaged;

                foreach (var tp in types)
                {
                    List<ITargetProvider> providers = null;
                    var info = tp.GetTypeInfo();
                    if (this._providers.TryGetValue(info, out providers))
                    {
                        if (providers.Contains(provider))
                            providers.Remove(provider);
                    }
                }
            }
        }

        /// <summary>
        /// Unregister provider.
        /// </summary>
        /// <param name="provider">The provider.</param>
        public void UnRegister(Guid providerId)
        {
            ITargetProvider prov;

            using (this._locatorLock.LockAsync().WaitResult())
            {
                if (this._providersById.TryGetValue(providerId, out prov))
                    this._providersById.Remove(providerId);
            }
            if (prov != null)
                UnRegister(prov);
        }

        /// <summary>
        /// Clears this instance.
        /// Except the LocalTargetProviderInstance.
        /// </summary>
        public void Clear()
        {
            using (this._locatorLock.LockAsync().WaitResult())
            {
                this._providers.Clear();
            }
            this.Register(new LocalTargetProvider());
        }

        #endregion
    }
}
